﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Authorization.Users;
using Abp.Domain.Repositories;
using Abp.Linq.Extensions;
using Abp.Organizations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using pandx.Mulan.OrganizationUnits.Dto;
using System.Linq.Dynamic.Core;
using Abp.Authorization;
using Abp.Collections.Extensions;
using Abp.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using pandx.Mulan.Authorization;
using pandx.Mulan.Authorization.Roles;
using pandx.Mulan.Authorization.Roles.Dto;
using pandx.Mulan.Authorization.Users;
using pandx.Mulan.Authorization.Users.Dto;
using pandx.Mulan.Dto;

namespace pandx.Mulan.OrganizationUnits
{
    public class OrganizationUnitAppService : MulanAppServiceBase, IOrganizationUnitAppService
    {
        private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;
        private readonly IRepository<UserOrganizationUnit, long> _userOrganizationUnitRepository;

        private readonly IRepository<OrganizationUnitRole, long> _organizationUnitRoleRepository;
        private readonly IRepository<UserRole, long> _userRoleRepository;

        public OrganizationUnitAppService(IRepository<OrganizationUnit, long> organizationUnitRepository,
            IRepository<UserOrganizationUnit, long> userOrganizationUnitRepository,
            IRepository<OrganizationUnitRole, long> organizationUnitRoleRepository,
            IRepository<UserRole, long> userRoleRepository)
        {
            _organizationUnitRepository = organizationUnitRepository;
            _userOrganizationUnitRepository = userOrganizationUnitRepository;
            _organizationUnitRoleRepository = organizationUnitRoleRepository;
            _userRoleRepository = userRoleRepository;
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_OrganizationUnits_Browse)]
        public async Task<ListResultDto<OrganizationUnitDto>> GetOrganizationUnits()
        {
            var organizationUnits = await _organizationUnitRepository.GetAllListAsync();
            var organizationUnitMemberCounts = await _userOrganizationUnitRepository.GetAll()
                .GroupBy(x => x.OrganizationUnitId)
                .Select(groupedUsers => new
                {
                    organizationUnitId = groupedUsers.Key,
                    count = groupedUsers.Count()
                }).ToDictionaryAsync(x => x.organizationUnitId, y => y.count);
            var organizationUnitRoleCount = await _organizationUnitRoleRepository.GetAll()
                .GroupBy(x => x.OrganizationUnitId)
                .Select(groupedUsers => new
                {
                    organizationUnitId = groupedUsers.Key,
                    count = groupedUsers.Count()
                }).ToDictionaryAsync(x => x.organizationUnitId, y => y.count);
            return new ListResultDto<OrganizationUnitDto>(organizationUnits.Select(ou =>
            {
                var dto = ObjectMapper.Map<OrganizationUnitDto>(ou);
                dto.MemberCount = organizationUnitMemberCounts.ContainsKey(ou.Id)
                    ? organizationUnitMemberCounts[ou.Id]
                    : 0;
                dto.RoleCount = organizationUnitRoleCount.ContainsKey(ou.Id) ? organizationUnitRoleCount[ou.Id] : 0;
                return dto;
            }).ToList());
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_OrganizationUnits_Browse)]
        public async Task<PagedResultDto<OrganizationUnitUserListDto>> ObtainOrganizationUnitUsers(
            ObtainOrganizationUnitUsersInput input)
        {
            if (input.Gender==null || input.Gender?.Count == 0)
            {
                input.Gender = new List<Gender> {Gender.Male, Gender.Female};
            }

            if (input.IsActive==null ||input.IsActive?.Count == 0)
            {
                input.IsActive = new List<bool> {true, false};
            }
            IQueryable<O> query = null;
            if (input.Id != 0)
            {
                query = from ouUser in _userOrganizationUnitRepository.GetAll()
                  
                    join user in UserManager.Users on ouUser.UserId equals user.Id
                    where ouUser.OrganizationUnitId == input.Id
                    select new O
                    {
                        UserOrganizationUnit = ouUser,
                        User = user,
                        OrganizationUnits = from ouu in user.OrganizationUnits
                            join ou in _organizationUnitRepository.GetAll() on ouu.OrganizationUnitId equals ou.Id
                            select ou
                    };
            }
            else
            {
                query = from user in UserManager.Users
                    select new O
                    {
                        UserOrganizationUnit = null,
                        User = user,
                        OrganizationUnits = from ouu in user.OrganizationUnits
                            join ou in _organizationUnitRepository.GetAll() on ouu.OrganizationUnitId equals ou.Id
                            select ou
                    };
            }

            query = query
                .Where(x=>input.Gender.Contains(x.User.Gender))
                .WhereIf(input.IsActive.Count>0,x=>input.IsActive.Contains(x.User.IsActive))
                .WhereIf(!input.Filter.IsNullOrWhiteSpace(), x => x.User.Name.Contains(input.Filter) ||
                                                                  x.User.UserName.Contains(input.Filter) ||
                                                                  x.User.EmailAddress.Contains(input.Filter) ||
                                                                  x.User.PhoneNumber.Contains(input.Filter) ||
                                                                  x.OrganizationUnits.Any(ou =>
                                                                      ou.DisplayName.Contains(input.Filter)));


            var totalCount = await query.CountAsync();
            var items = await query.OrderBy(input.Sorting).PageBy(input).ToListAsync();

            var organizationUnitUserListDtos = items.Select(item =>
            {
                var dto = ObjectMapper.Map<OrganizationUnitUserListDto>(item.User);
                dto.AddedTime = item.UserOrganizationUnit?.CreationTime;
                dto.OrganizationUnits = ObjectMapper.Map<List<UserListOrganizationUnitDto>>(item.OrganizationUnits);
                return dto;
            }).ToList();
            await FillRoleNames(organizationUnitUserListDtos);
            return new PagedResultDto<OrganizationUnitUserListDto>(totalCount, organizationUnitUserListDtos);
        }

        private async Task<PagedResultDto<OrganizationUnitRoleListDto>> ObtainOrganizationUnitRoles(
            ObtainOrganizationUnitRolesInput input)
        {
            var query = from ouRole in _organizationUnitRoleRepository.GetAll()
                join ou in _organizationUnitRepository.GetAll() on ouRole.OrganizationUnitId equals ou.Id
                join role in RoleManager.Roles on ouRole.RoleId equals role.Id
                select new
                {
                    ouRole,
                    role
                };
            var totalCount = await query.CountAsync();
            var items = await query.OrderBy(input.Sorting).PageBy(input).ToListAsync();
            return new PagedResultDto<OrganizationUnitRoleListDto>(totalCount, items.Select(item =>
            {
                var dto = ObjectMapper.Map<OrganizationUnitRoleListDto>(item.role);
                dto.AddedTime = item.ouRole.CreationTime;
                return dto;
            }).ToList());
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_OrganizationUnits_Create)]
        public async Task<OrganizationUnitDto> CreateOrganizationUnit(CreateOrganizationUnitInput input)
        {
            var organizationUnit = new OrganizationUnit(AbpSession.TenantId, input.DisplayName, input.ParentId);
            await OrganizationUnitManager.CreateAsync(organizationUnit);
            await CurrentUnitOfWork.SaveChangesAsync();

            return ObjectMapper.Map<OrganizationUnitDto>(organizationUnit);
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_OrganizationUnits_Update)]
        public async Task<OrganizationUnitDto> UpdateOrganizationUnit(UpdateOrganizationUnitInput input)
        {
            var organizationUnit = await _organizationUnitRepository.GetAsync(input.Id);
            organizationUnit.DisplayName = input.DisplayName;
            await OrganizationUnitManager.UpdateAsync(organizationUnit);

            return await CreateOrganizationUnitDto(organizationUnit);
        }
       
        private async Task<OrganizationUnitDto> MoveOrganizationUnit(MoveOrganizationUnitInput input)
        {
            await OrganizationUnitManager.MoveAsync(input.Id, input.NewParentId);
            return await CreateOrganizationUnitDto(await _organizationUnitRepository.GetAsync(input.Id));
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_OrganizationUnits_Delete)]
        public async Task DeleteOrganizationUnit(EntityDto<long> input)
        {
            await _userOrganizationUnitRepository.DeleteAsync(x => x.OrganizationUnitId == input.Id);
            await _organizationUnitRoleRepository.DeleteAsync(x => x.OrganizationUnitId == input.Id);
            await _organizationUnitRepository.DeleteAsync(input.Id);
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_OrganizationUnits_RemoveUser)]
        public async Task RemoveUserFromOrganizationUnit(UserToOrganizationUnitInput input)
        {
            await UserManager.RemoveFromOrganizationUnitAsync(input.UserId, input.OrganizationUnitId);
        }

        private async Task RemoveRoleFromOrganizationUnit(RoleToOrganizationUnitInput input)
        {
            await RoleManager.RemoveFromOrganizationUnitAsync(input.RoleId, input.OrganizationUnitId);
        }
        [AbpAuthorize(AppPermissions.Pages_Administration_OrganizationUnits_AddUser)]
        public async Task AddUsersToOrganizationUnit(UsersToOrganizationUnitInput input)
        {
            foreach (var userId in input.UserIds)
            {
                await UserManager.AddToOrganizationUnitAsync(userId, input.OrganizationUnitId);
            }
        }

       private  async Task AddRolesToOrganizationUnit(RolesToOrganizationUnitInput input)
        {
            foreach (var roleId in input.RoleIds)
            {
                await RoleManager.AddToOrganizationUnitAsync(roleId, input.OrganizationUnitId, AbpSession.TenantId);
            }
        }
       [AbpAuthorize(AppPermissions.Pages_Administration_OrganizationUnits_AddUser)]
        public async Task<PagedResultDto<UserListDto>> FindUsers(FindOrganizationUnitUsersInput input)
        {
            if (input.Gender==null || input.Gender?.Count == 0)
            {
                input.Gender = new List<Gender> {Gender.Male, Gender.Female};
            }

            if (input.IsActive==null || input.IsActive?.Count == 0)
            {
                input.IsActive = new List<bool> {true, false};
            }

            var userIdsInOrganizationUnit = _userOrganizationUnitRepository.GetAll()
                .Where(ouUser => ouUser.OrganizationUnitId == input.OrganizationUnitId)
                .Select(ouUser => ouUser.UserId);
            var query = from user in UserManager.Users
                    .Where(x => !userIdsInOrganizationUnit.Contains(x.Id))
                    .Where(x=>input.Gender.Contains(x.Gender))
                    .Where(x=>input.IsActive.Contains(x.IsActive))
                select new
                {
                    User = user,
                    OrganizationUnits = from ouUser in user.OrganizationUnits
                        join ou in _organizationUnitRepository.GetAll() on ouUser.OrganizationUnitId equals ou.Id
                        select ou
                };
            query = query
                .WhereIf(!input.Filter.IsNullOrWhiteSpace(),
                    x => x.User.Name.Contains(input.Filter) ||
                         x.User.UserName.Contains(input.Filter) ||
                         x.User.EmailAddress.Contains(input.Filter) ||
                         x.User.PhoneNumber.Contains(input.Filter) ||
                         x.OrganizationUnits.Any(ou => ou.DisplayName.Contains(input.Filter)));
            var userCount = await query.CountAsync();
            var userOrganizationUnits = await query.OrderBy(input.Sorting).PageBy(input).ToListAsync();
            var userListDtos = userOrganizationUnits.Select(item =>
            {
                var dto = ObjectMapper.Map<UserListDto>(item.User);
                dto.OrganizationUnits = ObjectMapper.Map<List<UserListOrganizationUnitDto>>(item.OrganizationUnits);
                return dto;
            }).ToList();
            await FillRoleNames(userListDtos);
            return new PagedResultDto<UserListDto>(userCount, userListDtos);
        }

        private  async Task<PagedResultDto<RoleListDto>> FindRoles(FindOrganizationUnitRolesInput input)
        {
            var roleIdsInOrganizationUnit = _organizationUnitRoleRepository.GetAll()
                .Where(ouRole => ouRole.OrganizationUnitId == input.OrganizationUnitId)
                .Select(ouRole => ouRole.RoleId);
            var query = RoleManager.Roles
                .Where(r => !roleIdsInOrganizationUnit.Contains(r.Id))
                .WhereIf(!input.Filter.IsNullOrWhiteSpace(),
                    r => r.DisplayName.Contains(input.Filter) ||
                         r.Name.Contains(input.Filter));
            var roleCount = await query.CountAsync();
            var roles = await query.OrderBy(r => r.DisplayName).PageBy(input).ToListAsync();

            return new PagedResultDto<RoleListDto>(roleCount, ObjectMapper.Map<List<RoleListDto>>(roles));
        }

        private async Task<OrganizationUnitDto> CreateOrganizationUnitDto(OrganizationUnit organizationUnit)
        {
            var dto = ObjectMapper.Map<OrganizationUnitDto>(organizationUnit);
            dto.MemberCount =
                await _userOrganizationUnitRepository.CountAsync(ouUser =>
                    ouUser.OrganizationUnitId == organizationUnit.Id);
            return dto;
        }

        [AbpAuthorize(AppPermissions.Pages_Administration_OrganizationUnits_Create,AppPermissions.Pages_Administration_OrganizationUnits_Update)]
        public async Task<ValidateOutput> ValidateOrganizationUnit(ValidateInput<string, long> input)
        {
         
            if (input.Id == 0)
            {
                var one = await _organizationUnitRepository.GetAll()
                    .SingleOrDefaultAsync(r => r.DisplayName == input.Value && r.ParentId == input.ParentId);
                if (one != null)
                {
                    return new ValidateOutput
                    {
                        Status = false,
                        Message = "同级别下已存在名称为 " + input.Value + " 的单位"
                    };
                }
                else
                {
                    return new ValidateOutput
                    {
                        Status = true,
                    };
                }
            }
            else
            {
                var one = await _organizationUnitRepository.GetAll().SingleOrDefaultAsync(r =>
                    r.DisplayName == input.Value && r.Id != input.Id && r.ParentId == input.ParentId);
                if (one != null)
                {
                    return new ValidateOutput
                    {
                        Status = false,
                        Message = "同级别下已存在名称为 " + input.Value + " 的单位"
                    };
                }
                else
                {
                    return new ValidateOutput
                    {
                        Status = true,
                    };
                }
            }
        }

        private async Task FillRoleNames(IReadOnlyCollection<UserListDto> userListDtos)
        {
            var userIds = userListDtos.Select(u => u.Id);
            var userRoles = await _userRoleRepository.GetAll()
                .Where(userRole => userIds.Contains(userRole.UserId))
                .Select(userRole => userRole).ToListAsync();

            var distinctRoleIds = userRoles.Select(userRole => userRole.RoleId).Distinct();
            foreach (var user in userListDtos)
            {
                var rolesOfUser = userRoles.Where(userRole => userRole.UserId == user.Id).ToList();
                user.Roles = ObjectMapper.Map<List<UserListRoleDto>>(rolesOfUser);
            }

            var roleNames = new Dictionary<int, string>();
            foreach (var roleId in distinctRoleIds)
            {
                var role = await RoleManager.FindByIdAsync(roleId.ToString());
                if (role != null)
                {
                    roleNames[roleId] = role.DisplayName;
                }
            }

            foreach (var userListDto in userListDtos)
            {
                foreach (var userListRoleDto in userListDto.Roles)
                {
                    if (roleNames.ContainsKey(userListRoleDto.RoleId))
                    {
                        userListRoleDto.RoleName = roleNames[userListRoleDto.RoleId];
                    }
                }

                userListDto.Roles = userListDto.Roles.Where(r => r.RoleName != null).OrderBy(r => r.RoleName).ToList();
            }
        }

        private async Task FillRoleNames(IReadOnlyCollection<OrganizationUnitUserListDto> organizationUnitUserListDtos)
        {
            var userIds = organizationUnitUserListDtos.Select(u => u.Id);
            var userRoles = await _userRoleRepository.GetAll()
                .Where(userRole => userIds.Contains(userRole.UserId))
                .Select(userRole => userRole).ToListAsync();

            var distinctRoleIds = userRoles.Select(userRole => userRole.RoleId).Distinct();
            foreach (var user in organizationUnitUserListDtos)
            {
                var rolesOfUser = userRoles.Where(userRole => userRole.UserId == user.Id).ToList();
                user.Roles = ObjectMapper.Map<List<UserListRoleDto>>(rolesOfUser);
            }

            var roleNames = new Dictionary<int, string>();
            foreach (var roleId in distinctRoleIds)
            {
                var role = await RoleManager.FindByIdAsync(roleId.ToString());
                if (role != null)
                {
                    roleNames[roleId] = role.DisplayName;
                }
            }

            foreach (var organizationUnitUserListDto in organizationUnitUserListDtos)
            {
                foreach (var userListRoleDto in organizationUnitUserListDto.Roles)
                {
                    if (roleNames.ContainsKey(userListRoleDto.RoleId))
                    {
                        userListRoleDto.RoleName = roleNames[userListRoleDto.RoleId];
                    }
                }

                organizationUnitUserListDto.Roles = organizationUnitUserListDto.Roles.Where(r => r.RoleName != null)
                    .OrderBy(r => r.RoleName).ToList();
            }
        }

        class O
        {
            public UserOrganizationUnit UserOrganizationUnit { get; set; }
            public User User { get; set; }
            public IEnumerable<OrganizationUnit> OrganizationUnits { get; set; }
        }
    }
}
